package org.hxzon.nioserver.impl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.hxzon.nioserver.Dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StandardAcceptor {
    private static final Logger logger = LoggerFactory.getLogger(StandardAcceptor.class);
    //
    private final Dispatcher dispatcher;
    private final InputHandlerFactory inputHandlerFactory;
    private final ServerSocketChannel listenSocket;
    private final Listener listener;
    private final List<Thread> threads = new ArrayList<Thread>();
    private volatile boolean running = true;

    //------------
    public StandardAcceptor(ServerSocketChannel listenSocket, Dispatcher dispatcher, InputHandlerFactory inputHandlerFactory) {
        this.listenSocket = listenSocket;
        this.dispatcher = dispatcher;
        this.inputHandlerFactory = inputHandlerFactory;

        listener = new Listener();
    }

    public StandardAcceptor(SocketAddress socketAddress, Dispatcher dispatcher, InputHandlerFactory inputHandlerFactory) throws IOException {
        this(ServerSocketChannel.open(), dispatcher, inputHandlerFactory);

        listenSocket.socket().bind(socketAddress);
    }

    public StandardAcceptor(int port, Dispatcher dispatcher, InputHandlerFactory inputHandlerFactory) throws IOException {
        this(new InetSocketAddress(port), dispatcher, inputHandlerFactory);
    }

    //------------
    private class Listener implements Runnable {
        public void run() {
            logger.trace("listener run thread");
            while (running) {
                try {
                    SocketChannel client = listenSocket.accept();

                    if (client == null) {
                        continue;
                    }

                    dispatcher.registerChannel(client, inputHandlerFactory.newHandler());

                } catch (ClosedByInterruptException e) {
                    logger.error("ServerSocketChannel closed by interrupt: " + e);
                    return;

                } catch (ClosedChannelException e) {
                    logger.error("Exiting, serverSocketChannel is closed: " + e, e);
                    return;

                } catch (Throwable t) {
                    logger.error("Exiting, Unexpected Throwable doing accept: " + t, t);

                    try {
                        listenSocket.close();
                    } catch (Throwable e1) { /* nothing */
                    }

                    return;
                }
            }
        }
    }

    //------------
    public synchronized Thread newThread() {
        Thread thread = new Thread(listener);

        threads.add(thread);

        thread.start();

        return thread;
    }

    public synchronized void shutdown() {
        running = false;

        for (Iterator<Thread> it = threads.iterator(); it.hasNext();) {
            Thread thread = it.next();

            if ((thread != null) && (thread.isAlive())) {
                thread.interrupt();
            }
        }

        for (Iterator<Thread> it = threads.iterator(); it.hasNext();) {
            Thread thread = (Thread) it.next();

            try {
                thread.join();
            } catch (InterruptedException e) {
                // nothing
            }

            it.remove();
        }

        try {
            listenSocket.close();
        } catch (IOException e) {
            logger.error("Caught an exception shutting down", e);
        }
    }
}
